<?php
/***************************************************************************
 * 
 * Copyright (c) 2010 , Inc. All Rights Reserved
 * $Id$:Response.php,2010/05/07 17:41:25 
 * 
 **************************************************************************/
 
 
 
/**
 * @file HttpResponse.php
 * @author huqingping
 * @date 2010/05/07 17:41:25
 * @version 1.0 
 * @brief 
 *  
 **/

final class HttpResponse
{
	private $app = null;

	var $template = null;
	var $exception = null;
	var $error = null;
	var $callback = null;

	var $headers = array();
	var $outputs = array();
	var $cookies = array();
	var $rawData = null;
	var $customErr = null;
	// 是否需要强制指定内容类型
	var $needSetContentType = true;
	private $results = array();


	function __construct(WebApp $app)
	{
		$this->app = $app;
	}

	function setHeader($header)
	{
		if ($this->needSetContentType && strpos(strtolower($header), 'content-type:') === 0 ) {
			$this->needSetContentType = false;
		}
		$this->headers[] = $header;
	}

	function setCookie($key,$value,$expires=null,$path='/',$domain=null,$secure=false,$httponly=false)
	{
		$this->cookies[] = array($key,$value,$expires,$path,$domain,$secure,$httponly);
	}
	function delCookie($key,$value='',$expires=1,$path='/',$domain=null,$secure=false,$httponly=false)
	{
		$this->cookies[] = array($key,$value,$expires,$path,$domain,$secure,$httponly);
	}

	function set($key,$value=null)
	{
		$this->outputs[$key] = $value;
	}
	
	/**
	 * 清理指定的键，使其不输出
	 * @param string $key 如果不传入任何值，则清除掉所有的设置。 如果传入内容，可以是单个key，或者数组，或者多参数传入
	 */
	function rmset()
	{
		if (func_num_args() == 0) {
			$this->outputs = array();
			return;
		}
		$key = func_get_arg(0);
		if (!is_array($key)) {
			$args = func_get_args();
		} else {
			$args = $key;
		}
		foreach($args as $k) {
			unset($this->outputs[$k]);
		}
	}

	function setRaw($data)
	{
		$this->rawData = $data;
	}

	function setException($ex)
	{
		$this->exception = $ex;
	}

	function setError($err)
	{
		$this->error = $err;
	}

	function setCallback($func)
	{
		if (!preg_match('/^[a-zA-Z_][a-zA-Z_0-9.]{0,128}$/',$func)) {
			throw new Exception('hapn.errcallback');
		}
		$this->callback = $func;
		//设置callback时需要修改输出格式和编码
		$this->app->request->of = 'json';
		$this->app->request->oe = 'utf-8';
	}

	function setView($path,$arr=array())
	{
		if ($arr) {
			$this->outputs = array_merge($this->outputs,$arr);
		}
		$this->template = $path;
		$this->app->request->of = 'html';
	}

	function redirect($url,$status='ok')
	{
		$this->setHeader('Location: '.$url);
		$this->setHapNHeader();
		$this->sendHeaders();
		//设置正常结束状态
		$this->app->endStatus = $status;
		exit;
	}

	function buildView($template,$userData,$output=false)
	{
		$engine = Conf::get('hapn.view','SmartyView');
		$this->app->timer->begin($engine);
		if (!class_exists($engine)) {
			$file = FR_ROOT.'view/'.$engine.'.php';
			require_once $file;
		}
		if (!class_exists($engine)) {
			throw new Exception("hapn.errclass view $engine not exist");
		}
		$view = new $engine();
		$view->init($this->app);
		$view->setArray($userData);
		if (!$output) {
			$result = $view->build($template);
		} else {
			$result = $view->display($template);
		}
		$this->app->timer->end($engine);    
		return $result;
	}

	function end()
	{
		$this->app->filterExecutor->executeFilter('output');
		//设置结束状态
		$this->app->endStatus = 'ok';
		exit;
	}

	function setNoCache()
	{
		$this->setHeader('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
		$this->setHeader('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
		$this->setHeader('Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0');
		$this->setHeader('Pragma: no-cache');
	}

	function customError($errcode)
	{
		$this->customErr = $errcode;
	}

	private function _buildContentType($of,$encoding)
	{
		if (!$this->needSetContentType) {
			return;
		}
		switch($of) {
		case 'json':
			$this->headers = array_merge(array('Content-Type: application/json; charset='.$encoding),$this->headers);
			break;
		case 'html':
			$this->headers = array_merge(array('Content-Type: text/html; charset='.$encoding),$this->headers);
			break;
		case 'xml':
			$this->headers = array_merge(array('Content-Type: text/xml; charset='.$encoding),$this->headers);
			break;
		default:
			$this->headers = array_merge(array('Content-Type: text/plain; charset='.$encoding),$this->headers);
		}
		
		$this->needSetContentType = false;
	}

	//请求的id设置到response header里
	public function setHapNHeader($errcode='suc')
	{
		global $__HapN_appid; 
		$header = sprintf('HapN: id=%s,%s',$this->app->appId,$__HapN_appid);
		
		if ($errcode != 'suc') {
			$method = 'r';
			$urls = explode('/',$this->app->request->url);
			if ($urls && strncmp($urls[count($urls)-1],'_',1) === 0) {
				//最后一节是否以下划线开头的
				$method = 'w';			
			}
			$header .= sprintf(',e=%s,m=%s', $errcode, $method);
			if (($retry = $this->app->request->get('retry'))) {
				$header .= ',r='.intval($retry);
			}
		}
		$this->setHeader($header);
	}
	
	/**
	 * 给HapN返回的结果中额外增加一些变量
	 * @param string $key
	 * @param string $value
	 */
	public function setHapNResult($key, $value)
	{
		if (!$key) return;
		$this->results[$key] = $value;
	}

	private function _getResult()
	{
		if ($this->exception) {
			$errcode = $this->exception->getMessage();
			if (($pos = strpos($errcode,' '))) {
				$errcode = substr($errcode,0,$pos); 
			}
			if (!preg_match('/^[a-zA-Z0-9\.\-_]{1,50}$/',$errcode)) {
				//普通的错误信息不能传到前端
				$errcode = 'hapn.fatal';
			}
			$result = array('err'=>$errcode);
			if ($errcode == 'hapn.u_input') {
				//输入check错误时，可以带些数据
				$result['data'] = $this->outputs;
				Logger::debug('input data error:%s',print_r($result['data'],true));
			}
		} elseif ($this->error) {
			$result = array('err'=>'hapn.fatal');
		} else {
			$result = array('err'=>'hapn.ok','data'=>$this->outputs);
		}
		foreach($this->results as $key => $value) {
			if (!isset($result[$key])) {
				$result[$key] = $value;
			}
		}
		return $result;
	}
	private function _formatResponse()
	{
		$result = $this->_getResult();
		$of = $this->app->request->of;
		$this->_buildContentType($of, $this->app->request->oe);
		if (!is_null($this->rawData)) {
			return $this->rawData;
		} elseif ($this->template) {
			return $this->buildView($this->template,$this->outputs,true);
		} else {
			//formatter
			if ($of == 'json') {
				if ($this->callback) {
					return $this->callback.'('.json_encode($result).')';
				} else {
					return json_encode($result);
				}
			} elseif ($of == 'xml') {
				require_once FR_ROOT.'util/XmlUtil.php';
				return XmlUtil::array2xml($result,$this->app->request->oe);
			} else {
				return print_r($result,true); 
			}
		}
	}	

	function sendHeaders()
	{
		$headers = $this->headers;
		if ($this->cookies) {
			//echo cookie
			foreach($this->cookies as $cookie) {
				call_user_func_array('setcookie',$cookie);
			}
		}
		if ($headers) {
			//echo header
			foreach($headers as $header) {
				header($header);
			}
		}
	}

	function send()
	{
		$this->setHapNHeader();
		$data = $this->_formatResponse();
		$this->sendHeaders();
		//获取缓冲数据
		$ob = ini_get('output_buffering');
		if ($ob && strtolower($ob) !== 'off') {
			$str = ob_get_clean();
			//忽略前后空白
			$data = trim($str).$data;
		}
		if ($data) {
			$outhandler = Conf::get('hapn.outputhandler',array());
			if ($outhandler && !is_array($outhandler)) {
				//也支持配置一个字符串
				$outhandler = array($outhandler);
			}
			if ($outhandler) {
				//调用全局输出处理器
				foreach($outhandler as $handler) {
					if (is_callable($handler)) {
						$data = call_user_func($handler,$data);
					} else {
						Logger::warning("outouthandler:%s can't call",is_array($handler)?$handler[1]:$handler);
					}
				}
			}
			echo $data;
		}
	}


}





/* vim: set ts=4 sw=4 sts=4 tw=100 noet: */
